/**
 * SocketIO.java
 * 
 * (C) 2011 - Mischa Spelt (m.spxxxelt@gmail.com - remove xxx)
 * 
 */
package com.mischa.network;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * SocketIO - an abstract base class that handles socket
 * reading and writing asynchronously using an int[] protocol.
 * 
 * @author Mischa Spelt
 */
public abstract class SocketIO extends Thread 
{
	/**
	 * The integer value that is used to signal end of message
	 */
	public int EndOfMessage = 0;

	/**
	 * The Socket object to read from and write to.
	 */
	private Socket _socket = null;
	
	/**
	 * Data input stream from the socket.
	 */
	private DataInputStream _in = null;
	
	/**
	 * Data output stream to the socket.
	 */
	private DataOutputStream _out = null;
	
	/**
	 * Constructor for SocketIO.
	 * Create a new object to read / write the given socket.
	 * 
	 * @param socket
	 * 		The socket to read / write.
	 */
	public SocketIO(Socket socket)
	{
		_socket = socket;
		
		try
		{
			_in = new DataInputStream(new BufferedInputStream(_socket.getInputStream(), 1024));
			_out = new DataOutputStream(new BufferedOutputStream(_socket.getOutputStream()));
		}
		catch (IOException e) 
		{
			exceptionRaised(e);						
		}
	}
	
	/**
	 * Override the Thread.run() method to start
	 * a new listener thread and wait for incoming messages.
	 */
	@Override
	public final void run() 
	{	
		ListenerThread listenerThread = null;
		
		try 
		{            			            
            listenerThread = new ListenerThread(_in, this);
            listenerThread.start();
            
            while(listenerThread.isAlive())
            {
            	// Wait for messages
            }
		}
		finally
		{			
			if(listenerThread != null)
			{
				listenerThread.stopListening();
			}
			
			try 
    		{				
				_in.close();
				_in = null;
				
				_out.close();
				_out = null;
			} 
    		catch (IOException e) 
    		{
				exceptionRaised(e);
			}  
		}
	}
	
	/**
	 * Method called when an exception has been raised while
	 * reading or writing data.
	 * Intended to be overridden by subclasses.
	 *  
	 * @param e
	 */
	public void exceptionRaised(Exception exception) 
	{
		exception.printStackTrace();		
	}

	/**
	 * Method called when a message has been received.
	 * Intended to be overridden by subclasses.
	 * 
	 * @param message
	 * 		The message that was received.
	 * 
	 * @param arguments
	 * 		Arguments to the message (may be an empty array, but is not null).
	 */
	public abstract void messageReceived(int message, int[] arguments);

	/**
	 * Send a message through the socket.
	 * 
	 * @param message
	 * 		The message to send.
	 * 
	 * @param arguments
	 * 		An array of arguments (possibly empty, but non-null).
	 */
	public final void sendMessage(int message, int[] arguments)
	{
		try {
			_out.writeInt(message);		
			for(int argument : arguments)
			{
				_out.writeInt(argument);
			}		
			_out.writeInt(EndOfMessage);
			_out.flush();
		} catch (IOException e) {
			exceptionRaised(e);
		}
	}
	
	/**
	 * Send a message without arguments through the socket.
	 * Is equivalent to sendMessage(message, new int[] { }).
	 * 
	 * @param message
	 * 		The message to send.
	 */
	public final void sendMessage(int message)
	{
		sendMessage(message, new int[] { });
	}
	
	/**
	 * Send a message without arguments through the socket.
	 * Is equivalent to sendMessage(message, new int[] { }).
	 * 
	 * @param message
	 * 		The message to send.
	 */
	public final void sendMessage(int message, int argument)
	{
		sendMessage(message, new int[] { argument });		
	}
}
